package faith.cloud.common.excel.utils;

import cn.idev.excel.ExcelWriter;
import cn.idev.excel.FastExcel;
import cn.idev.excel.write.builder.ExcelWriterSheetBuilder;
import cn.idev.excel.write.metadata.WriteSheet;
import cn.idev.excel.write.metadata.fill.FillConfig;
import cn.idev.excel.write.metadata.fill.FillWrapper;
import cn.idev.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;

import faith.cloud.common.excel.convert.ExcelBigNumberConvert;
import faith.cloud.common.excel.core.CellMergeStrategy;
import faith.cloud.common.excel.core.DefaultExcelListener;
import faith.cloud.common.excel.core.DropDownOptions;
import faith.cloud.common.excel.core.ExcelDownHandler;
import faith.cloud.common.excel.core.ExcelListener;
import faith.cloud.common.excel.core.ExcelResult;
import faith.cloud.common.excel.handler.DataWriteHandler;

import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;

import xyz.fivefaith.tool.boot.util.BootKit;
import xyz.fivefaith.tool.common.constant.StringPool;
import xyz.fivefaith.tool.common.util.Kit;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
 * Excel相关处理
 *
 * @author Lion Li
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ExcelUtil {

    /**
     * 同步导入(适用于小数据量)
     *
     * @param is 输入流
     * @return 转换后集合
     */
    public static <T> List<T> importExcel(InputStream is, Class<T> clazz) {
        return FastExcel.read(is).head(clazz).autoCloseStream(false).sheet().doReadSync();
    }

    /**
     * 使用校验监听器 异步导入 同步返回
     *
     * @param is 输入流
     * @param clazz 对象类型
     * @param isValidate 是否 Validator 检验 默认为是
     * @return 转换后集合
     */
    public static <T> ExcelResult<T> importExcel(
            InputStream is, Class<T> clazz, boolean isValidate) {
        DefaultExcelListener<T> listener = new DefaultExcelListener<>(isValidate);
        FastExcel.read(is, clazz, listener).sheet().doRead();
        return listener.getExcelResult();
    }

    /**
     * 使用自定义监听器 异步导入 自定义返回
     *
     * @param is 输入流
     * @param clazz 对象类型
     * @param listener 自定义监听器
     * @return 转换后集合
     */
    public static <T> ExcelResult<T> importExcel(
            InputStream is, Class<T> clazz, ExcelListener<T> listener) {
        FastExcel.read(is, clazz, listener).sheet().doRead();
        return listener.getExcelResult();
    }

    /**
     * 导出excel
     *
     * @param list 导出数据集合
     * @param sheetName 工作表的名称
     * @param clazz 实体类
     * @param response 响应体
     */
    public static <T> void exportExcel(
            List<T> list, String sheetName, Class<T> clazz, HttpServletResponse response) {
        try {
            resetResponse(sheetName, response);
            ServletOutputStream os = response.getOutputStream();
            exportExcel(list, sheetName, clazz, false, os, null);
        } catch (IOException e) {
            throw new RuntimeException("导出Excel异常");
        }
    }

    /**
     * 导出excel
     *
     * @param list 导出数据集合
     * @param sheetName 工作表的名称
     * @param clazz 实体类
     * @param response 响应体
     * @param options 级联下拉选
     */
    public static <T> void exportExcel(
            List<T> list,
            String sheetName,
            Class<T> clazz,
            HttpServletResponse response,
            List<DropDownOptions> options) {
        try {
            resetResponse(sheetName, response);
            ServletOutputStream os = response.getOutputStream();
            exportExcel(list, sheetName, clazz, false, os, options);
        } catch (IOException e) {
            throw new RuntimeException("导出Excel异常");
        }
    }

    /**
     * 导出excel
     *
     * @param list 导出数据集合
     * @param sheetName 工作表的名称
     * @param clazz 实体类
     * @param merge 是否合并单元格
     * @param response 响应体
     */
    public static <T> void exportExcel(
            List<T> list,
            String sheetName,
            Class<T> clazz,
            boolean merge,
            HttpServletResponse response) {
        try {
            resetResponse(sheetName, response);
            ServletOutputStream os = response.getOutputStream();
            exportExcel(list, sheetName, clazz, merge, os, null);
        } catch (IOException e) {
            throw new RuntimeException("导出Excel异常");
        }
    }

    /**
     * 导出excel
     *
     * @param list 导出数据集合
     * @param sheetName 工作表的名称
     * @param clazz 实体类
     * @param merge 是否合并单元格
     * @param response 响应体
     * @param options 级联下拉选
     */
    public static <T> void exportExcel(
            List<T> list,
            String sheetName,
            Class<T> clazz,
            boolean merge,
            HttpServletResponse response,
            List<DropDownOptions> options) {
        try {
            resetResponse(sheetName, response);
            ServletOutputStream os = response.getOutputStream();
            exportExcel(list, sheetName, clazz, merge, os, options);
        } catch (IOException e) {
            throw new RuntimeException("导出Excel异常");
        }
    }

    /**
     * 导出excel
     *
     * @param list 导出数据集合
     * @param sheetName 工作表的名称
     * @param clazz 实体类
     * @param os 输出流
     */
    public static <T> void exportExcel(
            List<T> list, String sheetName, Class<T> clazz, OutputStream os) {
        exportExcel(list, sheetName, clazz, false, os, null);
    }

    /**
     * 导出excel
     *
     * @param list 导出数据集合
     * @param sheetName 工作表的名称
     * @param clazz 实体类
     * @param os 输出流
     * @param options 级联下拉选内容
     */
    public static <T> void exportExcel(
            List<T> list,
            String sheetName,
            Class<T> clazz,
            OutputStream os,
            List<DropDownOptions> options) {
        exportExcel(list, sheetName, clazz, false, os, options);
    }

    /**
     * 导出excel
     *
     * @param list 导出数据集合
     * @param sheetName 工作表的名称
     * @param clazz 实体类
     * @param merge 是否合并单元格
     * @param os 输出流
     */
    public static <T> void exportExcel(
            List<T> list,
            String sheetName,
            Class<T> clazz,
            boolean merge,
            OutputStream os,
            List<DropDownOptions> options) {
        ExcelWriterSheetBuilder builder =
                FastExcel.write(os, clazz)
                        .autoCloseStream(false)
                        // 自动适配
                        .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                        // 大数值自动转换 防止失真
                        .registerConverter(new ExcelBigNumberConvert())
                        .registerWriteHandler(new DataWriteHandler(clazz))
                        .sheet(sheetName);
        if (merge) {
            // 合并处理器
            builder.registerWriteHandler(new CellMergeStrategy(list, true));
        }
        // 添加下拉框操作
        builder.registerWriteHandler(new ExcelDownHandler(options));
        builder.doWrite(list);
    }

    /**
     * 单表多数据模板导出 模板格式为 {.属性}
     *
     * @param filename 文件名
     * @param templatePath 模板路径 resource 目录下的路径包括模板文件名 例如: excel/temp.xlsx 重点: 模板文件必须放置到启动类对应的
     *     resource 目录下
     * @param data 模板需要的数据
     * @param response 响应体
     */
    public static <T> void exportTemplate(
            List<T> data, String filename, String templatePath, HttpServletResponse response) {
        try {
            if (CollectionUtils.isEmpty(data)) {
                throw new IllegalArgumentException("数据为空");
            }
            resetResponse(filename, response);
            ServletOutputStream os = response.getOutputStream();
            exportTemplate(data, templatePath, os);
        } catch (IOException e) {
            throw new RuntimeException("导出Excel异常");
        }
    }

    /**
     * 单表多数据模板导出 模板格式为 {.属性}
     *
     * @param templatePath 模板路径 resource 目录下的路径包括模板文件名 例如: excel/temp.xlsx 重点: 模板文件必须放置到启动类对应的
     *     resource 目录下
     * @param data 模板需要的数据
     * @param os 输出流
     */
    public static <T> void exportTemplate(List<T> data, String templatePath, OutputStream os) {

        // 1. 纯 JDK 方式读取 classpath 下的模板
        InputStream templateIn =
                Thread.currentThread().getContextClassLoader().getResourceAsStream(templatePath);

        if (templateIn == null) {
            throw new IllegalArgumentException("Template not found: " + templatePath);
        }

        // 2. 其余逻辑保持不变
        ExcelWriter excelWriter =
                FastExcel.write(os)
                        .withTemplate(templateIn)
                        .autoCloseStream(false)
                        .registerConverter(new ExcelBigNumberConvert())
                        .registerWriteHandler(new DataWriteHandler(data.getFirst().getClass()))
                        .build();

        WriteSheet writeSheet = FastExcel.writerSheet().build();
        FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();

        for (T d : data) {
            excelWriter.fill(d, fillConfig, writeSheet);
        }
        excelWriter.finish();
    }

    /**
     * 多表多数据模板导出 模板格式为 {key.属性}
     *
     * @param filename 文件名
     * @param templatePath 模板路径 resource 目录下的路径包括模板文件名 例如: excel/temp.xlsx 重点: 模板文件必须放置到启动类对应的
     *     resource 目录下
     * @param data 模板需要的数据
     * @param response 响应体
     */
    public static void exportTemplateMultiList(
            Map<String, Object> data,
            String filename,
            String templatePath,
            HttpServletResponse response) {
        try {
            if (MapUtils.isEmpty(data)) {
                throw new IllegalArgumentException("数据为空");
            }
            resetResponse(filename, response);
            ServletOutputStream os = response.getOutputStream();
            exportTemplateMultiList(data, templatePath, os);
        } catch (IOException e) {
            throw new RuntimeException("导出Excel异常");
        }
    }

    /**
     * 多sheet模板导出 模板格式为 {key.属性}
     *
     * @param filename 文件名
     * @param templatePath 模板路径 resource 目录下的路径包括模板文件名 例如: excel/temp.xlsx 重点: 模板文件必须放置到启动类对应的
     *     resource 目录下
     * @param data 模板需要的数据
     * @param response 响应体
     */
    public static void exportTemplateMultiSheet(
            List<Map<String, Object>> data,
            String filename,
            String templatePath,
            HttpServletResponse response) {
        try {
            if (CollectionUtils.isEmpty(data)) {
                throw new IllegalArgumentException("数据为空");
            }
            resetResponse(filename, response);
            ServletOutputStream os = response.getOutputStream();
            exportTemplateMultiSheet(data, templatePath, os);
        } catch (IOException e) {
            throw new RuntimeException("导出Excel异常");
        }
    }

    /**
     * 多表多数据模板导出 模板格式为 {key.属性}
     *
     * @param templatePath 模板路径 resource 目录下的路径包括模板文件名 例如: excel/temp.xlsx 重点: 模板文件必须放置到启动类对应的
     *     resource 目录下
     * @param data 模板需要的数据
     * @param os 输出流
     */
    public static void exportTemplateMultiList(
            Map<String, Object> data, String templatePath, OutputStream os) {
        // 1. 纯 JDK 读取 classpath 模板
        InputStream templateIn =
                Thread.currentThread().getContextClassLoader().getResourceAsStream(templatePath);

        if (templateIn == null) {
            throw new IllegalArgumentException("Template not found: " + templatePath);
        }

        // 2. FastExcel 导出
        ExcelWriter excelWriter =
                FastExcel.write(os)
                        .withTemplate(templateIn)
                        .autoCloseStream(false)
                        .registerConverter(new ExcelBigNumberConvert())
                        .build();

        WriteSheet writeSheet = FastExcel.writerSheet().build();

        // 3. 填充数据
        for (Map.Entry<String, Object> entry : data.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();

            FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();

            if (value instanceof Collection) {
                // 多行列表
                excelWriter.fill(
                        new FillWrapper(key, (Collection<?>) value), fillConfig, writeSheet);
            } else {
                // 单值
                excelWriter.fill(value, fillConfig, writeSheet);
            }
        }

        excelWriter.finish();
    }

    /**
     * 多sheet模板导出 模板格式为 {key.属性}
     *
     * @param templatePath 模板路径 resource 目录下的路径包括模板文件名 例如: excel/temp.xlsx 重点: 模板文件必须放置到启动类对应的
     *     resource 目录下
     * @param data 模板需要的数据
     * @param os 输出流
     */
    public static void exportTemplateMultiSheet(
            List<Map<String, Object>> data, String templatePath, OutputStream os) {
        // 1. 纯 JDK 读取 classpath 模板
        InputStream templateIn =
                Thread.currentThread().getContextClassLoader().getResourceAsStream(templatePath);

        if (templateIn == null) {
            throw new IllegalArgumentException("Template not found: " + templatePath);
        }
        ExcelWriter excelWriter =
                FastExcel.write(os)
                        .withTemplate(templateIn)
                        .autoCloseStream(false)
                        // 大数值自动转换 防止失真
                        .registerConverter(new ExcelBigNumberConvert())
                        .build();
        for (int i = 0; i < data.size(); i++) {
            WriteSheet writeSheet = FastExcel.writerSheet(i).build();
            for (Map.Entry<String, Object> map : data.get(i).entrySet()) {
                // 设置列表后续还有数据
                FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
                if (map.getValue() instanceof Collection) {
                    // 多表导出必须使用 FillWrapper
                    excelWriter.fill(
                            new FillWrapper(map.getKey(), (Collection<?>) map.getValue()),
                            fillConfig,
                            writeSheet);
                } else {
                    excelWriter.fill(map.getValue(), writeSheet);
                }
            }
        }
        excelWriter.finish();
    }

    /** 重置响应体 */
    private static void resetResponse(String sheetName, HttpServletResponse response)
            throws UnsupportedEncodingException {
        String filename = encodingFilename(sheetName);
        BootKit.setAttachmentResponseHeader(response, filename);
        response.setContentType(
                "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8");
    }

    /**
     * 解析导出值 0=男,1=女,2=未知
     *
     * @param propertyValue 参数值
     * @param converterExp 翻译注解
     * @param separator 分隔符
     * @return 解析后值
     */
    public static String convertByExp(String propertyValue, String converterExp, String separator) {
        StringBuilder propertyString = new StringBuilder();
        String[] convertSource = converterExp.split(StringPool.COMMA);
        for (String item : convertSource) {
            String[] itemArray = item.split("=");
            if (StringUtils.containsAny(propertyValue, separator)) {
                for (String value : propertyValue.split(separator)) {
                    if (itemArray[0].equals(value)) {
                        propertyString.append(itemArray[1]).append(separator);
                        break;
                    }
                }
            } else {
                if (itemArray[0].equals(propertyValue)) {
                    return itemArray[1];
                }
            }
        }
        return StringUtils.stripEnd(propertyString.toString(), separator);
    }

    /**
     * 反向解析值 男=0,女=1,未知=2
     *
     * @param propertyValue 参数值
     * @param converterExp 翻译注解
     * @param separator 分隔符
     * @return 解析后值
     */
    public static String reverseByExp(String propertyValue, String converterExp, String separator) {
        StringBuilder propertyString = new StringBuilder();
        String[] convertSource = converterExp.split(StringPool.COMMA);
        for (String item : convertSource) {
            String[] itemArray = item.split("=");
            if (StringUtils.containsAny(propertyValue, separator)) {
                for (String value : propertyValue.split(separator)) {
                    if (itemArray[1].equals(value)) {
                        propertyString.append(itemArray[0]).append(separator);
                        break;
                    }
                }
            } else {
                if (itemArray[1].equals(propertyValue)) {
                    return itemArray[0];
                }
            }
        }
        return StringUtils.stripEnd(propertyString.toString(), separator);
    }

    /** 编码文件名 */
    public static String encodingFilename(String filename) {
        return Kit.getSimpleUuid() + "_" + filename + ".xlsx";
    }
}
